// ast的节点定义以及匹配的正则
import {
	TEXT_TYPE,
	ELEMENT_TYPE,
	startTagOpen,
	startTagClose,
	endTag,
	attribute,
} from "./config";

// 解析模板让其变成ast
export function parseHTML(html) {
	// 每次解析就生成新的ast树
	let root = null; // ast的根
	let currentParent; // 标记当前的父标签是谁
	const stack = []; // 栈

	// 创建元素
	function createAstElement(tagName, attrs) {
		return {
			tag: tagName, // 标签名字
			type: ELEMENT_TYPE, // 标签类型
			children: [], // 子元素
			attrs, // 属性
			parent: null, // 指向的父元素
		};
	}

	// 解析开始标签及其属性,创建ast元素
	function start(tagName, attrs) {
		// 遇到开始标签就创建元素
		const element = createAstElement(tagName, attrs);
		// 判断有没有根元素,没有就用当前的作为根元素
		if (!root) root = element;
		// 把当前元素标记为父ast
		currentParent = element;
		// 把当前元素入栈
		stack.push(element);
	}

	// 解析文本
	function chars(text) {
		// 替换多个空格为一个空格
		text = text.replace(/\s/g, "");
		if (text) {
			// 让该文本的父节点的children加上它
			currentParent.children.push({
				text,
				type: TEXT_TYPE,
			});
		}
	}
	// 解析结束标签
	function end() {
		// 元素出栈,拿到最新进入栈的元素
		const element = stack.pop();
		// 修改指向父元素的指针为当前栈中元素的前一个元素
		currentParent = stack[stack.length - 1];
		// 如果不是根节点
		if (currentParent) {
			// 实现树的父子关系
			// 让当前元素的父元素指向currentParent
			element.parent = currentParent;
			// 让currentParent的children添加上当前的元素
			currentParent.children.push(element);
		}
	}

	// 判断是不是解析到头了
	while (html) {
		// 判断当前是不是以<开头,如果是就是标签,不是就是文本
		let textEnd = html.indexOf("<");
		// 解析标签,如果textEnd为0就表示是一个标签,<>或者</>
		if (textEnd === 0) {
			// 匹配开始标签
			const startTagMatch = parseStartTag(); // 获取标签名和属性
			// 解析完当前的东西就跳出一下当前的循环,继续向下解析
			if (startTagMatch) {
				const { tagName, attrs } = startTagMatch;
				// 入栈
				start(tagName, attrs);
				continue;
			}
			// 匹配结束标签
			const endTagMatch = html.match(endTag);
			if (endTagMatch) {
				// 吃掉结束标签
				advance(endTagMatch[0].length);
				// 弹栈
				end();
				continue;
			}
		}
		// 解析文本
		let text;
		if (textEnd >= 0) {
			// 截取文本
			text = html.substring(0, textEnd);
		}
		// 吃掉解析完的文本内容
		if (text) {
			advance(text.length);
			chars(text);
		}
	}

	// 吃掉解析过的模板
	function advance(n) {
		html = html.substring(n);
	}

	// 解析开始标签及其属性
	function parseStartTag() {
		// 匹配开始标签
		const start = html.match(startTagOpen);
		if (start) {
			const match = {
				tagName: start[1], // 标签名
				attrs: [], //属性
			};
			// 吃掉当前解析的过的内容
			advance(start[0].length);
			// 解析开始标签里面的属性
			let end, attr;
			while (
				!(end = html.match(startTagClose)) &&
				(attr = html.match(attribute))
			) {
				match.attrs.push({
					name: attr[1],
					value: attr[3] || attr[4] || attr[5],
				});
				// 吃掉解析过的内容
				advance(attr[0].length);
			}
			// 吃掉开始标签的>
			if (end) {
				advance(end[0].length);
			}
			return match;
		}
	}
	// 返回解析好的抽象语法树
	return root;
}
